﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BeanboxSoftware.BeanProxy.Tests
{
  [TestClass]
  public class EventAccessTest
  {
    [TestMethod]
    public void EventTest()
    {
      EventTestHelper<EventClass>();
      EventTestHelper<IEventClass>();
    }

    private void EventTestHelper<T>() where T : IEventClass
    {
      var ec = ProxyManager.CreateProxy<T>();

      int fires = 0;
      ec.Action0Event += () => { fires++; };
      ec.Action1Event += (a) => { fires++; };
      ec.Action2Event += (a, b) => { fires++; };
      ec.Action3Event += (a, b, c) => { fires++; };
      ec.Action4Event += (a, b, c, d) => { fires++; };
      ec.Action5Event += (a, b, c, d, e) => { fires++; };
      ec.Func0Event += () => { fires++; return ""; };
      ec.Func1Event += (a) => { fires++; return ""; };
      ec.Func2Event += (a, b) => { fires++; return ""; };
      ec.Func3Event += (a, b, c) => { fires++; return ""; };
      ec.Func4Event += (a, b, c, d) => { fires++; return ""; };
      ec.Func5Event += (a, b, c, d, e) => { fires++; return ""; };

      ProxyManager.LookupEvent<Action>(ec, "Action0Event").Fire();
      ProxyManager.LookupEvent<Action<int>>(ec, "Action1Event").Fire(1);
      ProxyManager.LookupEvent<Action<int, int>>(ec, "Action2Event").Fire(1, 2);
      ProxyManager.LookupEvent<Action<int, int, int>>(ec, "Action3Event").Fire(1, 2, 3);
      ProxyManager.LookupEvent<Action<int, int, int, int>>(ec, "Action4Event").Fire(1, 2, 3, 4);
      ProxyManager.LookupEvent<Action<int, int, int, int, int>>(ec, "Action5Event").Fire(1, 2, 3, 4, 5);

      ProxyManager.LookupEvent<Func<string>>(ec, "Func0Event").Fire();
      ProxyManager.LookupEvent<Func<int, string>>(ec, "Func1Event").Fire(1);
      ProxyManager.LookupEvent<Func<int, int, string>>(ec, "Func2Event").Fire(1, 2);
      ProxyManager.LookupEvent<Func<int, int, int, string>>(ec, "Func3Event").Fire(1, 2, 3);
      ProxyManager.LookupEvent<Func<int, int, int, int, string>>(ec, "Func4Event").Fire(1, 2, 3, 4);
      ProxyManager.LookupEvent<Func<int, int, int, int, int, string>>(ec, "Func5Event").Fire(1, 2, 3, 4, 5);

      Assert.AreEqual(12, fires, "fire count");
    }

    [TestMethod]
    public void EventMismatchTest()
    {
      var pm = ProxyManager.CreateProxy<IEventClass>();

      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Action>(pm, "Func0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Action<int, int>>(pm, "Func0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Action<int, int>>(pm, "Func0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Action<int, int, int>>(pm, "Func0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Action<int, int, int, int>>(pm, "Func0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Action<int, int, int, int, int>>(pm, "Func0Event"); });

      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Func<string>>(pm, "Action0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Func<int, int, string>>(pm, "Action0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Func<int, int, string>>(pm, "Action0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Func<int, int, int, string>>(pm, "Action0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Func<int, int, int, int, string>>(pm, "Action0Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Func<int, int, int, int, int, string>>(pm, "Action0Event"); });

      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Func<int, int, int, int, int, string>>(pm, "Func3Event"); });
      TestUtils.AssertExceptionThrown<InvalidCastException>(() => { ProxyManager.LookupEvent<Func<int, int, int, int, string, string>>(pm, "Func5Event"); });
    }

    [TestMethod]
    public void WarningsTest()
    {
      var ec = new EventClass();

      ec.Action0Event += () => { };
      ec.Action1Event += (a) => { };
      ec.Action2Event += (a, b) => { };
      ec.Action3Event += (a, b, c) => { };
      ec.Action4Event += (a, b, c, d) => { };
      ec.Action5Event += (a, b, c, d, e) => { };
      ec.Func0Event += () => { return ""; };
      ec.Func1Event += (a) => { return ""; };
      ec.Func2Event += (a, b) => { return ""; };
      ec.Func3Event += (a, b, c) => { return ""; };
      ec.Func4Event += (a, b, c, d) => { return ""; };
      ec.Func5Event += (a, b, c, d, e) => { return ""; };

      ec.InvokeAll();
    }

    [TestMethod]
    public void AllScopesTest()
    {
      var proxy = ProxyManager.CreateProxy<AllScopes>();

      var privateAccess = ProxyManager.LookupEvent<Action<string>>(proxy, "Private");
      object shared = null;
      object data = "hello private event";
      privateAccess.Add(s => { shared = s; });
      privateAccess.Fire((string)data);
      Assert.AreEqual(shared, data);

      var protectedNonAbstractAccess = ProxyManager.LookupEvent<Action<int>>(proxy, "ProtectedNonAbstract");
      data = 5;
      protectedNonAbstractAccess.Add(i => { shared = i; });
      protectedNonAbstractAccess.Fire((int)data);
      Assert.AreEqual(shared, data);

      var protectedAbstractAccess = ProxyManager.LookupEvent<Func<double>>(proxy, "ProtectedAbstract");
      data = 5.0;
      protectedAbstractAccess.Add(() => (double)data);
      shared = protectedAbstractAccess.Fire();
      Assert.AreEqual(shared, data);

      var PublicNonAbstractAccess = ProxyManager.LookupEvent<Action<bool>>(proxy, "PublicNonAbstract");
      data = true;
      PublicNonAbstractAccess.Add(b => { shared = b; });
      PublicNonAbstractAccess.Fire((bool)data);
      Assert.AreEqual(shared, data);

      var PublicAbstractAccess = ProxyManager.LookupEvent<Func<decimal, int>>(proxy, "PublicAbstract");
      data = 7.1m;
      PublicAbstractAccess.Add(d => { shared = d; return (int)d; });
      object result = PublicAbstractAccess.Fire((decimal)data);
      Assert.AreEqual(shared, data);
      Assert.AreEqual((int)((decimal)data), result);
    }

    [TestMethod]
    public void RemoveTest()
    {
      var proxy = ProxyManager.CreateProxy<AllScopes>();
      var ev = ProxyManager.LookupEvent<Action>(proxy, "Simple");

      int count = 0;
      var action = (Action)(() => { count++; });

      ev.Add(action);
      ev.Fire();

      Assert.AreEqual(1, count);

      count = 0;
      ev.Add(action); //2nd add
      ev.Fire();

      Assert.AreEqual(2, count);

      count = 0;
      ev.Remove(action); //back to 1
      ev.Fire();
      Assert.AreEqual(1, count);

      count = 0;
      var actionInt = ProxyManager.LookupEvent<Action<int>>(proxy, "PrivateInt");
      actionInt.Add(v => count += v);

      proxy.InvokePrivateInt(1);
      proxy.InvokePrivateInt(2);
      actionInt.Fire(3);

      Assert.AreEqual(6, count);
    }

    [TestMethod]
    public void ComplexEventTest()
    {
      var proxy = ProxyManager.CreateProxy<AllScopes>();
      var ea = ProxyManager.LookupEvent<ComplexEventType>(proxy, "ComplexEvent");

      ea.Add(OnComplexEvent);

      int rint = 1;
      decimal odec;
      string result = ea.Fire(ref rint, out odec, 2d, 4d);

      Assert.AreEqual("done", result);
      Assert.AreEqual(4m, odec);
      Assert.AreEqual(7, rint);

      rint = 1;
      proxy.InvokeComplexEvent(ref rint, out odec, 2d, 4d);

      Assert.AreEqual("done", result);
      Assert.AreEqual(4m, odec);
      Assert.AreEqual(7, rint);

      //using Access allows passing in an object[], which can simplify working with complex types
      odec = -1m;
      rint = 1;
      var args = new object[] { rint, odec, new double[] { 2d, 4d } };
      object rst = ea.Access.Fire(args);

      rint = (int)args[0];
      odec = (decimal)args[1];

      Assert.AreEqual("done", rst);
      Assert.AreEqual(4m, odec);
      Assert.AreEqual(7, rint);
    }
    private string OnComplexEvent(ref int rint, out decimal odec, params double[] doubles)
    {
      foreach (double d in doubles)
      {
        rint += (int)d;
      }

      odec = 4m;
      return "done";
    }
  }

  public delegate string ComplexEventType(ref int rint, out decimal odec, params double[] doubles);
  public abstract class AllScopes
  {
    private event ComplexEventType ComplexEvent;

    private event Action<string> Private;
    protected event Action<int> ProtectedNonAbstract;
    protected abstract event Func<double> ProtectedAbstract;
    public event Action<bool> PublicNonAbstract;
    public abstract event Func<decimal, int> PublicAbstract;

    public abstract event Action Simple;

    private event Action<int> PrivateInt;

    public AllScopes()
    {
      Private = null;
      ProtectedNonAbstract = null;
      PublicNonAbstract = null;

      if (Private != null) Private("");
      if (ProtectedNonAbstract != null) ProtectedNonAbstract(0);
      if (PublicNonAbstract != null) PublicNonAbstract(true);
    }

    public void InvokePrivateInt(int value)
    {
      PrivateInt(value);
    }

    public string InvokeComplexEvent(ref int rint, out decimal odec, params double[] doubles)
    {
      return ComplexEvent(ref rint, out odec, doubles);
    }
  }

  public interface IEventClass
  {
    event Action Action0Event;
    event Action<int> Action1Event;
    event Action<int, int> Action2Event;
    event Action<int, int, int> Action3Event;
    event Action<int, int, int, int> Action4Event;
    event Action<int, int, int, int, int> Action5Event;

    event Func<string> Func0Event;
    event Func<int, string> Func1Event;
    event Func<int, int, string> Func2Event;
    event Func<int, int, int, string> Func3Event;
    event Func<int, int, int, int, string> Func4Event;
    event Func<int, int, int, int, int, string> Func5Event;
  }

  public class EventClass : IEventClass
  {
    public event Action Action0Event;
    public event Action<int> Action1Event;
    public event Action<int, int> Action2Event;
    public event Action<int, int, int> Action3Event;
    public event Action<int, int, int, int> Action4Event;
    public event Action<int, int, int, int, int> Action5Event;
    public event Func<string> Func0Event;
    public event Func<int, string> Func1Event;
    public event Func<int, int, string> Func2Event;
    public event Func<int, int, int, string> Func3Event;
    public event Func<int, int, int, int, string> Func4Event;
    public event Func<int, int, int, int, int, string> Func5Event;

    public void InvokeAll()
    {
      Action0Event();
      Action1Event(1);
      Action2Event(1, 2);
      Action3Event(1, 2, 3);
      Action4Event(1, 2, 3, 4);
      Action5Event(1, 2, 3, 4, 5);
      Func0Event();
      Func1Event(1);
      Func2Event(1, 2);
      Func3Event(1, 2, 3);
      Func4Event(1, 2, 3, 4);
      Func5Event(1, 2, 3, 4, 5);
    }
  }
}


////

